/* $Id: BasicMonitor.java 113 2011-02-01 19:37:39Z altdotua@gmail.com $ */

package alt.djudge.frontend.server.models;

import java.util.Comparator;
import java.util.Collections;
import java.util.List;
import java.util.HashMap;
import java.util.Map;

import alt.djudge.frontend.server.datatypes.ContestProblemEntry;
import alt.djudge.frontend.server.datatypes.ContestUserEntry;
import alt.djudge.frontend.server.datatypes.SubmissionEntry;
import alt.djudge.frontend.server.datatypes.scores.ContestProblemScoreEntry;
import alt.djudge.frontend.server.datatypes.scores.ContestScoreEntry;
import alt.djudge.frontend.server.datatypes.scores.ContestUserScoreEntry;
import alt.djudge.frontend.server.datatypes.scores.ProblemUserScoreEntry;


public class BasicMonitor implements MonitorInterface
{
	private Long contestId;

	private ContestScoreEntry contestScoreEntry = new ContestScoreEntry();

	private Map<Long, ContestProblemScoreEntry> contestProblemScores = new HashMap<Long, ContestProblemScoreEntry>();

	private Map<Long, ContestUserScoreEntry> contestUserScores = new HashMap<Long, ContestUserScoreEntry>();

	private Map<Long, ProblemUserScoreEntry> problemUserScores = new HashMap<Long, ProblemUserScoreEntry>();

	private List<ContestUserEntry> users;

	private List<ContestProblemEntry> problems;
	
	private Map<String, Object> usersFilter = new HashMap<String, Object>();
	
	private Map<String, Object> submissionsFilter = new HashMap<String, Object>();
	
	private Map<String, Object> problemsFilter = new HashMap<String, Object>();
	
	private boolean includeDetetedSubmissions = false;

	private ContestUserScoreEntry getOrCreateUserScore(Long userId)
	{
		ContestUserScoreEntry entry = contestUserScores.get(userId);
		if (entry == null)
		{
			entry = new ContestUserScoreEntry();
			entry.setUserId(userId);
			contestUserScores.put(userId, entry);
		}
		return entry;
	}

	private ContestProblemScoreEntry getOrCreateProblemScore(Long problemId)
	{
		ContestProblemScoreEntry entry = contestProblemScores.get(problemId);
		if (entry == null)
		{
			entry = new ContestProblemScoreEntry();
			entry.setContestProblemId(problemId);
			contestProblemScores.put(problemId, entry);
		}
		return entry;
	}

	private Long getProblemUserKey(Long contestProblemId, Long userId)
	{
		return contestProblemId * 1000000000L + userId;
	}

	private ProblemUserScoreEntry getOrCreateProblemUserScore(
			Long contestProblemId, Long userId)
	{
		Long key = getProblemUserKey(contestProblemId, userId);
		ProblemUserScoreEntry entry = problemUserScores.get(key);
		if (entry == null)
		{
			entry = new ProblemUserScoreEntry();
			entry.setContestProblemId(contestProblemId);
			entry.setUserId(userId);
			problemUserScores.put(key, entry);
		}
		return entry;
	}

	public BasicMonitor(Long contestId)
	{
		this.contestId = contestId;
		usersFilter.put("contestId", contestId);
		users = ContestUsersModel.getEntries(usersFilter);
		buildMonitor();
	}
	
	public BasicMonitor(Long contestId, List<ContestUserEntry> monitoredUsers)
	{
		this.contestId = contestId;
		users = monitoredUsers;
		buildMonitor();
	}

	
	private void buildMonitor()
	{
		contestScoreEntry.setContestId(contestId);

		problemsFilter.put("contestId", contestId);
		
		submissionsFilter.put("contestId", contestId);
		submissionsFilter.put("active", true);
		submissionsFilter.put("flagFirstTestOnly", false);
		submissionsFilter.put("order", "contestTime ASC");
		if (!includeDetetedSubmissions)
			submissionsFilter.put("deleted", false);

		problems = ContestProblemsModel.getEntries(problemsFilter);
		
		for (ContestUserEntry contestUserEntry : users)
		{
			ContestUserScoreEntry contestUserScoreEntry = new ContestUserScoreEntry();
			contestUserScoreEntry.setUserId(contestUserEntry.getUserId());
			contestUserScores.put(contestUserEntry.getUserId(),
					contestUserScoreEntry);
		}

		List<SubmissionEntry> list = SubmissionsModel.getSubmissions(submissionsFilter);
		for (SubmissionEntry s : list)
		{
			// ProblemUserScore
			ProblemUserScoreEntry problemUserScoreEntry = getOrCreateProblemUserScore(
					s.getContestProblemId(), s.getUserId());
			problemUserScoreEntry.addSubmission(s);
		}

		for (ContestProblemEntry problem : problems)
		{
			getOrCreateProblemScore(problem.getId());
		}

		for (ContestUserEntry user : users)
		{
			ContestUserScoreEntry userScore = getOrCreateUserScore(user
					.getUserId());
			for (ContestProblemEntry problem : problems)
			{
				ProblemUserScoreEntry score = getOrCreateProblemUserScore(
						problem.getId(), user.getUserId());
				if (score.getAcceptedCount() > 0)
				{
					userScore.setTotalScore(userScore.getTotalScore()
							+ score.getMaxScore());
					userScore.setTotalTime(userScore.getTotalTime()
							+ score.getTotalTime());
					userScore.setUniqueAcceptedCount(userScore
							.getUniqueAcceptedCount() + 1);
				}
				else
				{
					userScore.setTotalScore(userScore.getTotalScore() + Math.max(score.getMaxScore(), 0));
				}
				userScore.setAcceptedCount(userScore.getAcceptedCount()
						+ score.getAcceptedCount());
				userScore.setRejectedCount(userScore.getRejectedCount()
						+ score.getRejectedCount());
			}
		}
	}
	
	@Override
	public Long getContestId()
	{
		return contestId;
	}

	@Override
	public Long getContestTime()
	{
		return 1111111L;
	}

	@Override
	public List<ContestProblemEntry> getProblems()
	{
		return problems;
	}

	@Override
	public List<ContestUserEntry> getUsers()
	{
		return users;
	}

	@Override
	public void sort(MonitorType monitorType)
	{
		if (MonitorType.ACM.equals(monitorType))
			sortACM();
		else if (MonitorType.IOI.equals(monitorType))
			sortIOI();
	}

	@Override
	public void sortACM()
	{
		Collections.sort(users, new Comparator<ContestUserEntry>()
		{
			@Override
			public int compare(ContestUserEntry o1, ContestUserEntry o2)
			{
				ContestUserScoreEntry s1 = BasicMonitor.this
						.getOrCreateUserScore(o1.getUserId());
				ContestUserScoreEntry s2 = BasicMonitor.this
						.getOrCreateUserScore(o2.getUserId());
				int k = -s1.getUniqueAcceptedCount().compareTo(
						s2.getUniqueAcceptedCount());
				if (k == 0)
				{
					k = s1.getTotalTime().compareTo(s2.getTotalTime());
				}
				return k;
			}
		});
	}

	public void sortUsername()
	{
		Collections.sort(users, new Comparator<ContestUserEntry>()
		{
			@Override
			public int compare(ContestUserEntry o1, ContestUserEntry o2)
			{
				return o1.getName().compareTo(o2.getName());
			}
		});
	}

	@Override
	public void sortIOI()
	{
		Collections.sort(users, new Comparator<ContestUserEntry>()
		{
			@Override
			public int compare(ContestUserEntry o1, ContestUserEntry o2)
			{
				ContestUserScoreEntry s1 = BasicMonitor.this
						.getOrCreateUserScore(o1.getUserId());
				ContestUserScoreEntry s2 = BasicMonitor.this
						.getOrCreateUserScore(o2.getUserId());
				int k = -s1.getTotalScore().compareTo(s2.getTotalScore());
				return k;
			}
		});
	}

	@Override
	public ContestScoreEntry getContestScore()
	{
		return contestScoreEntry;
	}

	@Override
	public ContestProblemScoreEntry getProblemScore(Long contestProblemId)
	{
		return contestProblemScores.get(contestProblemId);
	}

	@Override
	public ProblemUserScoreEntry getProblemUserScore(Long contestProblemId,
			Long userId)
	{
		Long key = getProblemUserKey(contestProblemId, userId);
		return problemUserScores.get(key);
	}

	@Override
	public ContestUserScoreEntry getUserScore(Long userId)
	{
		return contestUserScores.get(userId);
	}

	public ContestUserEntry getUser(Long userId)
	{
		// TODO: perfomance
		for (ContestUserEntry contestUserEntry : users)
			if (contestUserEntry.getUserId().equals(userId))
				return contestUserEntry;
		return null;
	}
}
